/**
 * Copyright 2007 Charlie Hubbard and Brandon Goodin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
package com.tbc.soa.json;


import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.tbc.soa.json.transformer.Transformer;
import com.tbc.soa.json.transformer.TransformerWrapper;
import com.tbc.soa.json.transformer.TypeTransformerMap;

/**
 * <p>
 * JSONSerializer is the main class for performing serialization of Java objects
 * to JSON. JSONSerializer by default performs a shallow serialization. While
 * this might seem strange there is a method to this madness. Shallow
 * serialization allows the developer to control what is serialized out of the
 * object graph. This helps with performance, but more importantly makes good OO
 * possible, fixes the circular reference problem, and doesn't require boiler
 * plate translation code. You don't have to change your object model to make
 * JSON work so it reduces your work load, and keeps you <a
 * href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>.
 * </p>
 * <p/>
 * <p>
 * Let's go through a simple example:
 * </p>
 * <p/>
 * 
 * <pre>
 *    JSONSerializer serializer = new JSONSerializer();
 *    return serializer.serialize( person );
 * &lt;p/&gt;
 * </pre>
 * <p/>
 * <p>
 * What this statement does is output the json from the instance of person. So
 * the JSON we might see for this could look like:
 * </p>
 * <p/>
 * 
 * <pre>
 *    { &quot;class&quot;: &quot;com.mysite.Person&quot;,
 *      &quot;firstname&quot;: &quot;Charlie&quot;,
 *      &quot;lastname&quot;: &quot;Rose&quot;,
 *      &quot;age&quot;, 23
 *      &quot;birthplace&quot;: &quot;Big Sky, Montanna&quot;
 *    }
 * &lt;p/&gt;
 * </pre>
 * <p>
 * In this case it's look like it's pretty standard stuff. But, let's say Person
 * had many hobbies (i.e. Person.hobbies is a java.util.List). In this case if
 * we executed the code above we'd still getTransformer the same output. This is
 * a very important feature of flexjson, and that is any instance variable that
 * is a Collection, Map, or Object reference won't be serialized by default.
 * This is what gives flexjson the shallow serialization.
 * </p>
 * <p/>
 * <p>
 * How would we include the <em>hobbies</em> field? Using the
 * {@link JSONSerializer#include} method allows us to include these fields in
 * the serialization process. Here is how we'd do that:
 * </p>
 * <p/>
 * 
 * <pre>
 *    return new JSONSerializer().include(&quot;hobbies&quot;).serialize( person );
 * &lt;p/&gt;
 * </pre>
 * <p/>
 * That would produce output like:
 * <p/>
 * 
 * <pre>
 *    { &quot;class&quot;: &quot;com.mysite.Person&quot;,
 *      &quot;firstname&quot;: &quot;Charlie&quot;,
 *      &quot;lastname&quot;: &quot;Rose&quot;,
 *      &quot;age&quot;, 23
 *      &quot;birthplace&quot;: &quot;Big Sky, Montanna&quot;,
 *      &quot;hobbies&quot;, [
 *          &quot;poker&quot;,
 *          &quot;snowboarding&quot;,
 *          &quot;kite surfing&quot;,
 *          &quot;bull riding&quot;
 *      ]
 *    }
 * &lt;p/&gt;
 * </pre>
 * <p/>
 * <p>
 * If the <em>hobbies</em> field contained objects, say Hobby instances, then a
 * shallow copy of those objects would be performed. Let's go further and say
 * <em>hobbies</em> had a List of all the people who enjoyed this hobby. This
 * would create a circular reference between Person and Hobby. Since the shallow
 * copy is being performed on Hobby JSONSerialize won't serialize the people
 * field when serializing Hobby instances thus breaking the chain of circular
 * references.
 * </p>
 * <p/>
 * <p>
 * But, for the sake of argument and illustration let's say we wanted to send
 * the <em>people</em> field in Hobby. We can do the following:
 * </p>
 * <p/>
 * 
 * <pre>
 *    return new JSONSerializer().include(&quot;hobbies.people&quot;).serialize( person );
 * &lt;p/&gt;
 * </pre>
 * <p/>
 * <p>
 * JSONSerializer is smart enough to know that you want <em>hobbies</em> field
 * included and the <em>people</em> field inside hobbies' instances too. The dot
 * notation allows you do traverse the object graph specifying instance fields.
 * But, remember a shallow copy will stop the code from getting into an infinte
 * loop.
 * </p>
 * <p/>
 * <p>
 * You can also use the exclude method to exclude fields that would be included.
 * Say we have a User object. It would be a serious security risk if we sent the
 * password over the network. We can use the exclude method to prevent the
 * password field from being sent.
 * </p>
 * <p/>
 * 
 * <pre>
 *   return new JSONSerialize().exclude(&quot;password&quot;).serialize(user);
 * &lt;p/&gt;
 * </pre>
 * <p/>
 * <p>
 * JSONSerializer will also pay attention to any method or field annotated by
 * {@link com.tbc.soa.json.JSON}. You can include and exclude fields permenantly using
 * the annotation. This is good like in the case of User.password which should
 * never ever be sent through JSON. However, fields like <em>hobbies</em> or
 * <em>favoriteMovies</em> depends on the situation so it's best NOT to annotate
 * those fields, and use the {@link JSONSerializer#include} method.
 * </p>
 * <p/>
 * <p>
 * In a shallow copy only these types of instance fields will be sent:
 * <strong>String</strong>, <strong>Date</strong>, <strong>Number</strong>,
 * <strong>Boolean</strong>, <strong>Character</strong>, <strong>Enum</strong>,
 * <strong>Object</strong> and <strong>null</strong>. Subclasses of Object will
 * be serialized except for Collection or Arrays. Anything that would cause a N
 * objects would not be sent. All types will be excluded by default. Fields
 * marked static or transient are not serialized.
 * </p>
 * <p>
 * Includes and excludes can include wildcards. Wildcards allow you to do things
 * like exclude all class attributes. For example *.class would remove the class
 * attribute that all objects have when serializing. A open ended wildcard like
 * * would cause deep serialization to take place. Be careful with that one.
 * Although you can limit it's depth with an exclude like *.foo. The order of
 * evaluation of includes and excludes is the order in which you called their
 * functions. First call to those functions will cause those expressions to be
 * evaluated first. The first expression to match a path that action will be
 * taken thus short circuiting all other expressions defined later.
 * </p>
 * <p>
 * Transforers are a new addition that allow you to modify the values that are
 * being serialized. This allows you to create different output for certain
 * conditions. This is very important in web applications. Say you are saving
 * your text to the DB that could contain &lt; and &gt;. If you plan to add that
 * content to your HTML page you'll need to escape those characters.
 * Transformers allow you to do this. Flexjson ships with a simple HTML encoder
 * {@link com.tbc.soa.json.transformer.HtmlEncoderTransformer}. Transformers are
 * specified in dot notation just like include and exclude methods, but it
 * doesn't support wildcards.
 * </p>
 * <p>
 * JSONSerializer is safe to use the serialize() methods from two seperate
 * threads. It is NOT safe to use combination of
 * {@link JSONSerializer#include(String[])}
 * {@link JSONSerializer#transform(Transformer, String[])}, or
 * {@link JSONSerializer#exclude(String[])} from multiple threads at the same
 * time. It is also NOT safe to use {@link JSONSerializer#serialize(Object)} and
 * include/exclude/transform from multiple threads. The reason for not making
 * them more thread safe is to boost performance. Typical use case won't call
 * for two threads to modify the JsonSerializer at the same type it's trying to
 * serialize.
 * </p>
 */
public class JSONSerializer {

	public final static char[] HEX = "0123456789ABCDEF".toCharArray();

	private TypeTransformerMap typeTransformerMap = new TypeTransformerMap(
			TransformerUtil.getDefaultTypeTransformers());
	// private Map<Path, Transformer> pathTransformerMap = new HashMap<Path,
	// Transformer>();

	private List<PathExpression> pathExpressions = new ArrayList<PathExpression>();

	private boolean prettyPrint;
	private String rootName;

	// OutputHander Configuration

	/**
	 * format output with indentations
	 * 
	 * @param prettyPrint
	 *            - should out put cleanfly formatted Json
	 * @return JsonSerializer for chaining configuration
	 */
	public JSONSerializer prettyPrint(boolean prettyPrint) {
		this.prettyPrint = prettyPrint;
		return this;
	}

	/**
	 * This wraps the resulting JSON in a javascript object that contains a
	 * single field named rootName. This is great to use in conjunction with
	 * other libraries like EXTJS whose data models require them to be wrapped
	 * in a JSON object.
	 * 
	 * @param rootName
	 *            - name to assign to root object
	 * @return this JsonSerializer for chaining configurations
	 */
	public JSONSerializer rootName(String rootName) {
		this.rootName = rootName;
		return this;
	}

	// SERIALIZATION

	/**
	 * This performs a shallow serialization of the target instance. It uses a
	 * StringBuilder to write output to.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @return returns JSON as a String
	 */
	public String serialize(Object target) {
		return serialize(target, SerializationType.SHALLOW,
				new StringBuilderOutputHandler(new StringBuilder()));
	}

	/**
	 * This performs a shallow serialization of the target instance and passes
	 * the generated JSON into the provided Writer. This can be used to stream
	 * JSON back to a browser rather than wait for it to all complete and then
	 * dump it all at once like the StringBufferOutputHandler and
	 * StringBuilderOutputHandler
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - Writer to write output to
	 */
	public void serialize(Object target, Writer out) {
		serialize(target, SerializationType.SHALLOW, new WriterOutputHandler(
				out));
	}

	/**
	 * This performs a shallow serialization of the target instance and passes
	 * the generated JSON into the provided StringBuilder.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - StringBuilder to write output to
	 * @return returns JSON as a String
	 */
	public String serialize(Object target, StringBuilder out) {
		return serialize(target, SerializationType.SHALLOW,
				new StringBuilderOutputHandler(out));
	}

	/**
	 * This performs a shallow serialization of the target instance and passes
	 * the generated JSON into the provided StringBuffer.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - StringBuffer to write output to
	 * @return returns JSON as a String
	 */
	public String serialize(Object target, StringBuffer out) {
		return serialize(target, SerializationType.SHALLOW,
				new StringBufferOutputHandler(out));
	}

	/**
	 * This performs a shallow serialization of the target instance and passes
	 * the generated JSON into the provided OutputHandler.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - OutputHandler to write output to
	 * @return returns JSON as a String
	 */
	public String serialize(Object target, OutputHandler out) {
		return serialize(target, SerializationType.SHALLOW, out);
	}

	/**
	 * This performs a deep serialization of the target instance. It will
	 * include all collections, maps, and arrays by default so includes are
	 * ignored except if you want to include something being excluded by an
	 * annotation. Excludes are honored. However, cycles in the target's graph
	 * are NOT followed. This means some members won't be included in the JSON
	 * if they would create a cycle. Rather than throwing an exception the cycle
	 * creating members are simply not followed. This uses a StringBuilder to
	 * output JSON to.
	 * 
	 * @param target
	 *            the instance to serialize to JSON.
	 * @return returns JSON as a String
	 */
	public String deepSerialize(Object target) {
		return serialize(target, SerializationType.DEEP,
				new StringBuilderOutputHandler(new StringBuilder()));
	}
	
	public String deepSerialize(Object target, boolean serializeWithClassInfo) {
		return serialize(target, SerializationType.DEEP,
				new StringBuilderOutputHandler(new StringBuilder()), serializeWithClassInfo);
	}

	public String deepSerializeInOrder(Object header, Object body, Object tail) {
		// 256 byte is a proper size for most cases.
		StringBuilderOutputHandler out = new StringBuilderOutputHandler(
				new StringBuilder(256));

		return serializeInOrder(header, body, tail, SerializationType.DEEP, out);
	}

	/**
	 * This performs a deep serialization of the target instance and passes the
	 * generated JSON into the provided Writer. This can be used to stream JSON
	 * back to a browser rather than wait for it to all complete and then dump
	 * it all at once like the StringBufferOutputHandler and
	 * StringBuilderOutputHandler
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - Writer
	 */
	public void deepSerialize(Object target, Writer out) {
		serialize(target, SerializationType.DEEP, new WriterOutputHandler(out));
	}


	/**
	 * This performs a deep serialization of the target instance and passes the
	 * generated JSON into the provided StringBuilder.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - StringBuilder
	 * @return returns JSON as a String
	 */
	public String deepSerialize(Object target, StringBuilder out) {
		return serialize(target, SerializationType.DEEP,
				new StringBuilderOutputHandler(out));
	}

	/**
	 * This performs a deep serialization of the target instance and passes the
	 * generated JSON into the provided StringBuffer.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - StringBuffer
	 * @return returns JSON as a String
	 */
	public String deepSerialize(Object target, StringBuffer out) {
		return serialize(target, SerializationType.DEEP,
				new StringBufferOutputHandler(out));
	}

	/**
	 * This performs a deep serialization of the target instance and passes the
	 * generated JSON into the provided OutputHandler.
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param out
	 *            - OutputHandler to write to
	 * @return returns JSON as a String
	 */
	public String deepSerialize(Object target, OutputHandler out) {
		return serialize(target, SerializationType.DEEP, out);
	}

	/**
	 * 
	 * @param target
	 *            - the instance to serialize to JSON
	 * @param serializationType
	 *            - serialize deep or shallow
	 * @param out
	 *            - output handler
	 * @return returns JSON as a String
	 */
	protected String serialize(Object target,
			SerializationType serializationType, OutputHandler out) {
		return serialize(target, serializationType, out, true);
	}

	protected String serialize(Object target,
			SerializationType serializationType, OutputHandler out, boolean serializeWithClassInfo) {
		String output = "";
		// initialize context
		JSONContext context = JSONContext.get();
		context.setRootName(rootName);
		context.setPrettyPrint(prettyPrint);
		context.setOut(out);
		context.serializationType(serializationType);
		context.setTypeTransformers(typeTransformerMap);
		// context.setPathTransformers(pathTransformerMap);
		context.setPathExpressions(pathExpressions);
		context.setSerializeWithClassInfo(serializeWithClassInfo);
		try {

			// initiate serialization of target tree
			String rootName = context.getRootName();
			if (rootName == null || rootName.trim().equals("")) {
				context.transform(target);
			} else {
				context.writeOpenObject();
				context.writeName(rootName);
				context.transform(target);
				context.writeCloseObject();
			}

			output = context.getOut().toString();
		} finally {
			// cleanup context
			JSONContext.cleanup();

		}
		return output;
	}

	protected String serializeInOrder(Object header, Object body, Object tail,
			SerializationType serializationType, OutputHandler out) {
		String output = "";
		// initialize context
		JSONContext context = JSONContext.get();
		context.setRootName(rootName);
		context.setPrettyPrint(false);
		context.setOut(out);
		context.serializationType(serializationType);
		context.setTypeTransformers(typeTransformerMap);
		// context.setPathTransformers(pathTransformerMap);
		context.setPathExpressions(pathExpressions);

		try {

			// initiate serialization of target tree
			context.writeOpenObject();

			context.writeName("header");
			context.transform(header);

			context.setPrettyPrint(true);
			context.writeComma();
			context.setPrettyPrint(false);

			context.writeName("body");
			context.transform(body);

			if (tail != null) {
				context.setPrettyPrint(true);
				context.writeComma();
				context.setPrettyPrint(false);

				context.writeName("tail");
				context.transform(tail);
			}

			context.writeCloseObject();
			output = context.getOut().toString();
		} finally {
			// cleanup context
			JSONContext.cleanup();

		}
		return output;
	}

	// TRANSFORMER CONFIGURATIONS

	/**
	 * This adds a Transformer used to manipulate the value of all the fields
	 * you give it. Fields can be in dot notation just like
	 * {@link JSONSerializer#include} and {@link JSONSerializer#exclude }
	 * methods. However, transform doesn't support wildcards. Specifying more
	 * than one field allows you to add a single instance to multiple fields.
	 * It's there for handiness. :-)
	 * 
	 * @param transformer
	 *            the instance used to transform values
	 * @param fields
	 *            the paths to the fields you want to transform. They can be in
	 *            dot notation.
	 * @return Hit you back with the JSONSerializer for method chain goodness.
	 */
	// public JSONSerializer transform(Transformer transformer, String...
	// fields) {
	// transformer = new TransformerWrapper(transformer);
	// for (String field : fields) {
	// if (field.length() == 0) {
	// pathTransformerMap.put(new Path(), transformer);
	// } else {
	// pathTransformerMap.put(new Path(field.split("\\.")), transformer);
	// }
	// }
	// return this;
	// }

	/**
	 * This adds a Transformer used to manipulate the value of all fields that
	 * match the type.
	 * 
	 * @param transformer
	 *            the instance used to transform values
	 * @param types
	 *            you want to transform.
	 * @return Hit you back with the JSONSerializer for method chain goodness.
	 */
	public JSONSerializer transform(Transformer transformer, Class... types) {

		transformer = new TransformerWrapper(transformer);

		for (Class type : types) {
			typeTransformerMap.put(type, transformer);
		}

		return this;
	}

	// INCLUDE/EXCLUDE CONFIGURATION

	protected void addExclude(String field) {
		int index = field.lastIndexOf('.');
		if (index > 0) {
			PathExpression expression = new PathExpression(field.substring(0,
					index), true);
			if (!expression.isWildcard()) {
				pathExpressions.add(expression);
			}
		}
		pathExpressions.add(new PathExpression(field, false));
	}

	protected void addInclude(String field) {
		pathExpressions.add(new PathExpression(field, true));
	}

	/**
	 * This takes in a dot expression representing fields to exclude when
	 * serialize method is called. You can hand it one or more fields. Example
	 * are: "password", "bankaccounts.number", "people.socialsecurity", or
	 * "people.medicalHistory". In exclude method dot notations will only
	 * exclude the final field (i.e. rightmost field). All the fields to the
	 * left of the last field will be included. In order to exclude the
	 * medicalHistory field we have to include the people field since people
	 * would've been excluded anyway since it's a Collection of Person objects.
	 * The order of evaluation is the order in which you call the exclude
	 * method. The first call to exclude will be evaluated before other calls to
	 * include or exclude. The field expressions are evaluated in order you pass
	 * to this method.
	 * 
	 * @param fields
	 *            one or more field expressions to exclude.
	 * @return this instance for method chaining.
	 */
	public JSONSerializer exclude(String... fields) {
		for (String field : fields) {
			addExclude(field);
		}
		return this;
	}

	/**
	 * This takes in a dot expression representing fields to include when
	 * serialize method is called. You can hand it one or more fields. Examples
	 * are: "hobbies", "hobbies.people", "people.emails", or
	 * "character.inventory". When using dot notation each field between the
	 * dots will be included in the serialization process. The order of
	 * evaluation is the order in which you call the include method. The first
	 * call to include will be evaluated before other calls to include or
	 * exclude. The field expressions are evaluated in order you pass to this
	 * method.
	 * 
	 * @param fields
	 *            one or more field expressions to include.
	 * @return this instance for method chaining.
	 */
	public JSONSerializer include(String... fields) {
		for (String field : fields) {
			addInclude(field);
		}
		return this;
	}

	// INCLUDE/EXCLUDE TEST/DEBUG HOOKS

	/**
	 * Return the fields included in serialization. These fields will be in dot
	 * notation.
	 * 
	 * @return A List of dot notation fields included in serialization.
	 */
	public List<PathExpression> getIncludes() {
		List<PathExpression> expressions = new ArrayList<PathExpression>();
		for (PathExpression expression : pathExpressions) {
			if (expression.isIncluded()) {
				expressions.add(expression);
			}
		}
		return expressions;
	}

	/**
	 * Return the fields excluded from serialization. These fields will be in
	 * dot notation.
	 * 
	 * @return A List of dot notation fields excluded from serialization.
	 */
	public List<PathExpression> getExcludes() {
		List<PathExpression> excludes = new ArrayList<PathExpression>();
		for (PathExpression expression : pathExpressions) {
			if (!expression.isIncluded()) {
				excludes.add(expression);
			}
		}
		return excludes;
	}

	/**
	 * Sets the fields included in serialization. These fields must be in dot
	 * notation. This is just here so that JSONSerializer can be treated like a
	 * bean so it will integrate with Spring or other frameworks. <strong>This
	 * is not ment to be used in code use include method for that.</strong>
	 * 
	 * @param fields
	 *            the list of fields to be included for serialization. The
	 *            fields arg should be a list of strings in dot notation.
	 */
	public void setIncludes(List<String> fields) {
		for (String field : fields) {
			pathExpressions.add(new PathExpression(field, true));
		}
	}

	/**
	 * Sets the fields excluded in serialization. These fields must be in dot
	 * notation. This is just here so that JSONSerializer can be treated like a
	 * bean so it will integrate with Spring or other frameworks. <strong>This
	 * is not ment to be used in code use exclude method for that.</strong>
	 * 
	 * @param fields
	 *            the list of fields to be excluded for serialization. The
	 *            fields arg should be a list of strings in dot notation.
	 */
	public void setExcludes(List<String> fields) {
		for (String field : fields) {
			addExclude(field);
		}
	}

}
